import Taro from "@tarojs/taro"
import { IContentType } from "@/utils/enum"
import { wxLogin, isLoginRequest, authLs } from "@/utils/auth"
import { withStore } from "@/store/index"
import { reset } from "@/store/actions/auth"
import { dateFormat } from "@/utils/common"
import { buildUUID } from "@/utils/uuid"

/** 接口最多重试次数 */
const maxTryCount = 5
/** 记录每次接口连续请求401失败的映射, 当某个接口连续401失败次数超出最多次数直接失败，成功后需清除该对应记录
 * @type Map<接口路径, tryCount>
 */
const tryRequestMap = new Map<string, number>()

interface IWaitingTask {
  /** 任务请求配置 */
  reqOpts: IFetchOpts
  /** 当前任务Promise#resolve */
  resolve: IFn
  /** 当前任务Promise#reject */
  reject: IFn
}
const waitingTasks: IWaitingTask[] = []
/** 是否正在发起登录 */
let isAuthing = false
/** 执行所有等待队列请求任务 */
function runWaitingTasks(fulfilled: boolean) {
  setIsAuthing(false)
  !fulfilled && terminateTasks()
  while (waitingTasks.length) {
    const next = waitingTasks.shift() as IWaitingTask
    request({
      ...next.reqOpts,
      success: next.resolve,
      fail: next.reject
    })
  }
}
function terminateTasks() {
  waitingTasks.length = 0
  tryRequestMap.clear()
}
export const getIsAuthing = () => isAuthing
export const setIsAuthing = (state: boolean) => (isAuthing = state)
interface IReqConfig {
  baseUrl: string
}

type IFetchOpts = Taro.request.Option & {
  showLoading?: boolean
  urlCustom?: boolean
}
export const getReqConfig = function (): IReqConfig {
  return {
    baseUrl: __BASE_URL__
  }
}
export function request<R extends Recordable = any>(
  opts: IFetchOpts
): Promise<Taro.request.SuccessCallbackResult<IApiRelative.IResSchema<R>>> {
  return new Promise((resolve, reject) => {
    // 存在问题 TODO
    // 如果 login code 失败，会导致其他接口请求不了

    // 如果正在请求登录，添加请求任务到等待队列。需要排除登录接口本身。
    if (getIsAuthing() && !isLoginRequest(opts.url)) {
      waitingTasks.push({
        reject,
        resolve,
        reqOpts: opts
      })
      return
    }
    const { url, method = "GET", data, showLoading, header = {}, success, fail, complete } = opts
    const token = authLs.getToken()
    const baseHeader: Taro.General.IAnyObject = {
      // ajax标识
      "X-Requested-With": "XMLHttpRequest",
      Authorization: token ? "Bearer " + token : ""
    }
    if (method === "POST") {
      baseHeader["content-type"] = IContentType.urlencoded
    }
    if (showLoading) {
      Taro.showLoading({
        mask: true,
        title: "加载中"
      })
    }
    return Taro.request<IApiRelative.IResSchema<R>>({
      url: opts.urlCustom === true ? url : getReqConfig().baseUrl + url,
      method,
      data,
      header: {
        ...baseHeader,
        ...header
      },
      success: function (res: Taro.request.SuccessCallbackResult<IApiRelative.IResSchema<R>>) {
        console.log("req success", res, opts)
        if (showLoading) {
          Taro.hideLoading()
        }
        if (res.statusCode !== 200) {
          if (res.statusCode === 401) {
            const store = withStore()
            store.dispatch(reset())
            Taro.showToast({
              title: "请重新登录",
              icon: "none",
              mask: false
            })
            // 限制重试
            const tryCount = tryRequestMap.get(opts.url)
            if (typeof tryCount === "undefined") {
              tryRequestMap.set(opts.url, 1)
            } else {
              if (tryCount > maxTryCount) {
                Taro.showToast({
                  title: "请求失败，请稍候再试",
                  icon: "none",
                  mask: false
                })
                reject(res)
                tryRequestMap.delete(opts.url)
                return
              } else {
                tryRequestMap.set(opts.url, tryCount + 1)
              }
            }
            // 将当前任务添加等待队列
            waitingTasks.push({
              reject,
              resolve,
              reqOpts: opts
            })
            if (!getIsAuthing()) {
              setIsAuthing(true)
              wxLogin(runWaitingTasks)
            }
          } else {
            Taro.showToast({
              title: "请求失败，请稍候再试",
              icon: "none",
              mask: false
            })
            reject(res)
          }
          return
        }
        // 接口请求成功/非401失败重置tryRequestMap
        if (tryRequestMap.has(opts.url)) {
          tryRequestMap.delete(opts.url)
        }
        if (!res.data.success) {
          if (res.data.message) {
            Taro.showToast({
              title: res.data.message || "请求失败，请稍候再试",
              icon: "none",
              mask: false
            })
          }
          return reject(res)
        }
        success && success(res)
        resolve(res)
      },
      fail: function (res: Taro.General.CallbackResult) {
        console.log("req fail", res, opts)
        if (showLoading) {
          Taro.hideLoading()
        }

        Taro.showToast({
          title: res.errMsg || "请求失败，请稍候再试",
          icon: "none",
          mask: false
        })

        fail && fail(res)
        reject(res)
      },
      complete: function (res: Taro.General.CallbackResult) {
        complete && complete(res)
      }
    })
  })
}
/**
 * send a Get request
 * @param url request url
 * @param query query params
 * @param opts request opts
 * @param urlPrefix request url prefix, default `/api`
 * @returns RequestTask
 */
export const httpGet = function <R = any>(
  url: string,
  query: string | Taro.General.IAnyObject = {},
  opts: Omit<IFetchOpts, "url" | "method" | "data"> = {},
  urlPrefix = "/api"
): Promise<Taro.request.SuccessCallbackResult<IApiRelative.IResSchema<R>>> {
  return request<R>({ url: urlPrefix + url, method: "GET", data: query, ...opts })
}

/**
 * send a Post request
 * @param url request url
 * @param query post data
 * @param opts request opts
 * @param urlPrefix request url prefix, default `/api`
 * @returns RequestTask
 */
export const httpPost = function <R = any>(
  url: string,
  data: any = {},
  opts: Omit<IFetchOpts, "url" | "method" | "data"> = {},
  urlPrefix = "/api"
): Promise<Taro.request.SuccessCallbackResult<IApiRelative.IResSchema<R>>> {
  const fullUrl = url.startsWith("http") ? url : urlPrefix + url
  return request<R>({ url: fullUrl, method: "POST", data, ...opts })
}

/**
 * send a Put request
 * @param url request url
 * @param query Put data
 * @param opts request opts
 * @param urlPrefix request url prefix, default `/api`
 * @returns RequestTask
 */
export const httpPut = function <R = any>(
  url: string,
  data: any = {},
  opts: Omit<IFetchOpts, "url" | "method" | "data"> = {},
  urlPrefix = "/api"
): Promise<Taro.request.SuccessCallbackResult<IApiRelative.IResSchema<R>>> {
  return request<R>({ url: urlPrefix + url, method: "PUT", data, ...opts })
}

/**
 * send a Delete request
 * @param url request url
 * @param query Delete data
 * @param opts request opts
 * @param urlPrefix request url prefix, default `/api`
 * @returns RequestTask
 */
export const httpDelete = function <R = any>(
  url: string,
  data: any = {},
  opts: Omit<IFetchOpts, "url" | "method" | "data"> = {},
  urlPrefix = "/api"
): Promise<Taro.request.SuccessCallbackResult<IApiRelative.IResSchema<R>>> {
  return request<R>({ url: urlPrefix + url, method: "DELETE", data, ...opts })
}

export type IUploadFileOptions = {
  formData?: Taro.uploadFile.Option["formData"]
  /**
   * @default true
   */
  withCredentials?: boolean
  /**
   * @default false
   */
  urlCustom?: boolean
  /**
   * @default /api
   */
  urlPrefix?: string
}
let uploadCount = 0
const defaultOptions: IUploadFileOptions = {
  urlCustom: false,
  urlPrefix: "/api",
  withCredentials: true,
  formData: {
    UserType: "gp",
    Guid: buildUUID(),
    Date: dateFormat(),
    BussCode: "1000",
    Chunks: 0,
    Chunk: 0
  }
}
/** 文件上传*/
export const uploadFile = function (
  url,
  filePath,
  name = "File",
  options: IUploadFileOptions = {}
) {
  const { formData, ...normalOptions } = options
  options = Object.assign({}, defaultOptions, normalOptions)
  Taro.showLoading({
    title: "上传中"
  })
  uploadCount++
  return Taro.uploadFile({
    url: options.urlCustom === true ? url : getReqConfig().baseUrl + options.urlPrefix + url,
    filePath: filePath,
    name: name,
    header: {
      Authorization: options.withCredentials ? "Bearer " + authLs.getToken() : ""
    },
    formData: Object.assign({}, defaultOptions.formData, formData),
    complete: function () {
      uploadCount--
      if (uploadCount === 0) {
        Taro.hideLoading()
      }
    }
  })
}
/** 接口是否请求成功 */
export function requestIsFullfilled(
  res: Taro.request.SuccessCallbackResult<IApiRelative.IResSchema>
): boolean {
  return res.data && res.data.success
}
